import base64
import json
import secrets
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi import APIRouter, Depends, HTTPException, status
from typing import List, Tuple, Optional, Type
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request

# from fastapi.responses import HTMLResponse

from tortoise.signals import post_save, post_delete

from ..models import *
from ..utils.note_schema import MultiNotesId

router = APIRouter(
    prefix="/note",
    tags=["note"],
)


security = HTTPBasic()


def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
    correct_username = secrets.compare_digest(credentials.username, "seadog")
    correct_password = secrets.compare_digest(credentials.password, "vimatremote")
    if not (correct_username and correct_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect email or password",
            headers={"WWW-Authenticate": "Basic"},
        )
    return credentials.username


@router.post("/delmulti/")
async def del_multi(data: MultiNotesId):
    for id in data.ids:
        try:
            obj = await Note.get(id=id)
            data = await noteOut_pydantic.from_tortoise_orm(obj)
            await obj.delete()
        # @todo 最好分类下，比如找不到obj的情况
        except:
            pass
    return {"message": "success"}


@router.get("/")
async def get_all_note():
    return await noteOut_pydantic.from_queryset(Note.all())


@router.post("/viminfo")
async def create_note_security(note: noteIn_pydantic, username: str = Depends((get_current_username))):
    note.body = base64.b64decode(note.dict(exclude_unset=True)["body"]).decode()
    new_obj = await Note.create(**note.dict(exclude_unset=True))
    # return await noteIn_pydantic.from_tortoise_orm(new_obj)
    return "success"


@router.post("/")
async def create_note(note: noteIn_pydantic):
    new_obj = await Note.create(**note.dict(exclude_unset=True))
    return await noteIn_pydantic.from_tortoise_orm(new_obj)


@router.get("/{id}")
async def note_detail(id: int):
    obj = await Note.get(id=id)
    return await noteOut_pydantic.from_tortoise_orm(obj)


@router.patch("/{id}")
async def note_update(id: int, data: noteIn_pydantic):
    data = data.dict(exclude_unset=True)
    obj = await Note.get(id=id)
    await obj.update_from_dict(data)
    await obj.save()
    return await noteIn_pydantic.from_tortoise_orm(obj)


@router.delete("/{id}")
async def note_delete(id: int):
    obj = await Note.get(id=id)
    data = await noteOut_pydantic.from_tortoise_orm(obj)
    await obj.delete()
    return {"delete": data}


class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []
        self.clients_id: List[WebSocket] = []

    async def connect(self, websocket: WebSocket, client_id: str):
        await websocket.accept()
        self.active_connections.append(websocket)
        self.clients_id.append(client_id)

    def disconnect(self, websocket: WebSocket):
        client_id = self.active_connections.index(websocket)
        self.active_connections.remove(websocket)
        self.clients_id.pop(client_id)

    async def send_personal_message(self, message: str, websocket: WebSocket):
        await websocket.send_text(message)

    # @done 广播时排除自己
    async def broadcast_exclude_talker(self, message: str, current_talker: WebSocket):
        for connection in self.active_connections:
            if not connection is current_talker:
                await connection.send_text(message)

    async def broadcast_all(self, message: str):
        for connection in self.active_connections:
            # await connection.send_text(message)
            await connection.send_json(message)


manager = ConnectionManager()
global_websocket = []


# @todo using enum to limit operation to : add / del / clients-update
async def process_instance(operation: str, instance: Note):
    if operation in ("add", "del"):
        note_obj_out = await noteOut_pydantic.from_tortoise_orm(instance)
        data = {"operation": operation}
        data.update(json.loads(note_obj_out.json()))  # note_obj_out.json() 返回结果为str!!!
        return data


async def send_online_client_info():
    data = [{"id": i, "source": str(j.client.host) + ":" + str(j.client.port)} for i, j in zip(manager.clients_id, manager.active_connections)]
    data = {"clients": data, "operation": "client-info"}
    # print(data)
    await manager.broadcast_all(data)

    # @router.get('/test/')
    # async def get():
    #     return HTMLResponse(html)


@router.websocket("/ws/update/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
    await manager.connect(websocket, client_id)
    await send_online_client_info()
    try:
        while True:
            # @done client should info server,operation type: del,add. server will react accordingly and return proper data
            # @note 由于通过signal出发 websocket给client发送数据，这里的数据接受没有意义了
            data = await websocket.receive_text()
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await send_online_client_info()
        # data = [{"id": i, "source": str(j.client.host) + ":" + str(j.client.port)} for i, j in zip(manager.clients_id, manager.active_connections)]
        # data = {"clients": data, "operation": "client-info"}
        # # print(data)
        # await manager.broadcast_all(data)


@post_save(Note)
async def create_business(
    sender: "Type[Note]",
    instance: Note,
    created: bool,
    using_db: "Optional[BaseDBAsyncClient]",
    update_fields: List[str],
) -> None:
    if created:
        data = await process_instance("add", instance)
        if data:
            await manager.broadcast_all(data)


@post_delete(Note)
async def signal_post_delete(sender: "Type[Note]", instance: Note, using_db: "Optional[BaseDBAsyncClient]") -> None:
    data = await process_instance("del", instance)
    if data:
        await manager.broadcast_all(data)
